/*
 *  Copyright 2004 Blandware (http://www.blandware.com)
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package com.blandware.atleap.webapp.taglib.core.html;

import com.blandware.atleap.webapp.util.core.ApplicationResources;
import org.apache.commons.validator.Arg;
import org.apache.commons.validator.Field;
import org.apache.commons.validator.Form;
import org.apache.commons.validator.ValidatorAction;
import org.apache.commons.validator.ValidatorResources;
import org.apache.commons.validator.Var;
import org.apache.commons.validator.util.ValidatorUtils;
import org.apache.struts.action.ActionMapping;
import org.apache.struts.config.ModuleConfig;
import org.apache.struts.taglib.TagUtils;
import org.apache.struts.validator.ValidatorPlugIn;

import javax.servlet.jsp.JspException;
import javax.servlet.jsp.PageContext;
import javax.servlet.jsp.tagext.SimpleTagSupport;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.StringTokenizer;

/**
 * <p>Replacement of <code>org.apache.struts.taglib.html.JavascriptValidatorTag</code>.
 * Renders javascript functions for client-side validations based on the
 * validation rules loaded by the <code>ValidatorPlugIn</code>
 * </p>
 * <p>This tag was copied from the Struts HTML tag library and modified to meet
 * our requirements</p>
 * <p>
 * Allowed attributes are:
 * <ul>
 * <li>
 * <b>formName</b> - the name of the form that corresponds with the action name
 * in struts-config.xml. If <b>dynamicJavascript</b> is "true", then <b>formName</b>
 * is required and specifies name of form for which to generate dynamic code.
 * </li>
 * <li>
 * <b>page</b> - the current page number of a multi-part form. Only valid when
 * the <b>formName</b> attribute is set.
 * </li>
 * <li>
 * <b>method</b> - this will be used as is for the JavaScript validation method
 * name if it has a value.  This is the method name of the main JavaScript
 * method that the form calls to perform validations.
 * </li>
 * <li>
 * <b>staticJavascript</b> - the static JavaScript methods will only be printed
 * if this is set to "true".
 * </li>
 * <li>
 * <b>dynamicJavascript</b> - the dynamic JavaScript objects will only be
 * generated if this is set to "true".
 * </li>
 * <li>
 * <b>htmlComment</b> - the JavaScript methods will be enclosed with html
 * comments if this is set to "true".
 * </li>
 * <li>
 * <b>src</b> - the src attribute for html script element (used to include an
 * external script resource). The <b>src</b> attribute is only recognized when
 * the <b>formName</b> attribute is specified.
 * </li>
 * <li>
 * <b>cdata</b> - hide JavaScript methods in a CDATA section for XHTML when "true".
 * </li>
 * <li>
 * <b>scriptLanguage</b> - whether to include language attribute in the &lt;script&gt;
 * element. This property is ignored in XHTML mode.
 * </li>
 * </ul>
 * </p>
 * <p><a href="JavascriptTag.java.html"><i>View Source</i></a></p>
 *
 * @author Sergey Zubtsovskiy <a href="mailto:sergey.zubtsovskiy@blandware.com">&lt;sergey.zubtsovskiy@blandware.com&gt;</a>
 * @version $Revision: 1.6 $ $Date: 2006/08/03 10:08:23 $
 * @jsp.tag name="javascript"
 * body-content="empty"
 * @see org.apache.struts.taglib.html.JavascriptValidatorTag
 */
public class JavascriptTag extends SimpleTagSupport {

	/**
	 * A Comparator to use when sorting ValidatorAction objects.
	 */
	private static final Comparator actionComparator = new Comparator() {
		public int compare(Object o1, Object o2) {

			ValidatorAction va1 = (ValidatorAction) o1;
			ValidatorAction va2 = (ValidatorAction) o2;

			if ( (va1.getDepends() == null || va1.getDepends().length() == 0)
			        && (va2.getDepends() == null || va2.getDepends().length() == 0) ) {
				return 0;

			} else if (
			        (va1.getDepends() != null && va1.getDepends().length() > 0)
			        && (va2.getDepends() == null || va2.getDepends().length() == 0) ) {
				return 1;

			} else if (
			        (va1.getDepends() == null || va1.getDepends().length() == 0)
			        && (va2.getDepends() != null && va2.getDepends().length() > 0) ) {
				return -1;

			} else {
				return va1.getDependencyList().size() - va2.getDependencyList().size();
			}
		}
	};

	/**
	 * The start of the HTML comment hiding JavaScript from old browsers.
	 */
	protected static final String HTML_BEGIN_COMMENT = "\n<!-- Begin \n";

	/**
	 * The end of the HTML comment hiding JavaScript from old browsers.
	 */
	protected static final String HTML_END_COMMENT = "//End --> \n";

	// ----------------------------------------------------------- Properties

	/**
	 * The name of the form that corresponds with the action name
	 * in struts-config.xml. Specifying a form name places a
	 * &lt;script&gt; &lt;/script&gt; around the javascript.
	 */
	protected String formName = null;

	/**
	 * formName is used for both Javascript and non-javascript validations.
	 * For the javascript validations, there is the possibility that we will
	 * be rewriting the formName (if it is a ValidatorActionForm instead of just
	 * a ValidatorForm) so we need another variable to hold the formName just for
	 * javascript usage.
	 */
	protected String jsFormName = null;

	/**
	 * The line ending string.
	 */
	protected static String lineEnd = System.getProperty("line.separator");

	/**
	 * The current page number of a multi-part form.
	 * Only valid when the formName attribute is set.
	 */
	protected Integer page = new Integer(0);

	/**
	 * This will be used as is for the JavaScript validation method name if it has a value.  This is
	 * the method name of the main JavaScript method that the form calls to perform validations.
	 */
	protected String methodName = null;

	/**
	 * Include language attribute in the &lt;script&gt; element.  This property is
	 * ignored in XHTML mode.
	 */
	protected Boolean scriptLanguage = Boolean.TRUE;

	/**
	 * The static JavaScript methods will only be printed if this is set to "true".
	 */
	protected Boolean staticJavascript = Boolean.TRUE;

	/**
	 * The dynamic JavaScript objects will only be generated if this is set to "true".
	 */
	protected Boolean dynamicJavascript = Boolean.TRUE;

	/**
	 * The src attribute for html script element (used to include an external script
	 * resource). The src attribute is only recognized
	 * when the formName attribute is specified.
	 */
	protected String src = null;

	/**
	 * The JavaScript methods will be enclosed with html comments if this is set to "true".
	 */
	protected Boolean htmlComment = Boolean.TRUE;

	/**
	 * Hide JavaScript methods in a CDATA section for XHTML when "true".
	 */
	protected Boolean cdata = Boolean.TRUE;

	/**
	 * Gets the key (form name) that will be used
	 * to retrieve a set of validation rules to be
	 * performed on the bean passed in for validation.
	 *
     * @return form name
	 * @jsp.attribute required="false"
	 * rtexprvalue="true"
	 * type="java.lang.String"
	 * description="Form name that will be used to retrieve a set of validation rules"
	 */
	public String getFormName() {
		return formName;
	}

	/**
	 * Sets the key (form name) that will be used
	 * to retrieve a set of validation rules to be
	 * performed on the bean passed in for validation.
	 * Specifying a form name places a
	 * &lt;script&gt; &lt;/script&gt; tag around the javascript.
	 */
	public void setFormName(String formName) {
		this.formName = formName;
	}

	/**
     * Returns form name
     *
	 * @return form name
     * @see #jsFormName
	 */
	public String getJsFormName() {
		return jsFormName;
	}

	/**
     * Sets form name
     *
	 * @param jsFormName form name to set
     * @see #jsFormName
	 */
	public void setJsFormName(String jsFormName) {
		this.jsFormName = jsFormName;
	}

	/**
	 * Gets the current page number of a multi-part form.
	 * Only field validations with a matching page numer
	 * will be generated that match the current page number.
	 * Only valid when the formName attribute is set.
	 *
     * @return page number
	 * @jsp.attribute required="false"
	 * rtexprvalue="true"
	 * type="java.lang.Integer"
	 * description="Gets the current page number of a multi-part form"
	 */
	public Integer getPage() {
		return page;
	}

	/**
	 * Sets the current page number of a multi-part form.
	 * Only field validations with a matching page numer
	 * will be generated that match the current page number.
	 * Only valid when the formName attribute is set.
     *
     * @param page number to set
	 */
	public void setPage(Integer page) {
		this.page = page;
	}

	/**
	 * Gets the method name that will be used for the Javascript
	 * validation method name if it has a value.  This overrides
	 * the auto-generated method name based on the key (form name)
	 * passed in.
	 *
     * @return method name
	 * @jsp.attribute required="false"
	 * rtexprvalue="true"
	 * type="java.lang.String"
	 * description="Gets the method name that will be used for the Javascript validation method name if it has a value"
	 */
	public String getMethod() {
		return methodName;
	}

	/**
	 * Sets the method name that will be used for the Javascript
	 * validation method name if it has a value.  This overrides
	 * the auto-generated method name based on the key (form name)
	 * passed in.
     *
     * @param methodName method name to set
	 */
	public void setMethod(String methodName) {
		this.methodName = methodName;
	}

	/**
	 * Gets whether or not to generate the static
	 * JavaScript.  If this is set to 'true', which
	 * is the default, the static JavaScript will be generated.
	 *
     * @return whether to generate the static JavaScript
	 * @jsp.attribute required="false"
	 * rtexprvalue="true"
	 * type="java.lang.Boolean"
	 * description="Gets whether or not to generate the static JavaScript"
	 */
	public Boolean getStaticJavascript() {
		return staticJavascript;
	}

	/**
	 * Sets whether or not to generate the static
	 * JavaScript.  If this is set to 'true', which
	 * is the default, the static JavaScript will be generated.
     *
     * @param staticJavascript whether to generate the static JavaScript
	 */
	public void setStaticJavascript(Boolean staticJavascript) {
		this.staticJavascript = staticJavascript;
	}

	/**
	 * Gets whether or not to generate the dynamic
	 * JavaScript.  If this is set to 'true', which
	 * is the default, the dynamic JavaScript will be generated.
	 *
     * @return whether to generate the dynamic JavaScript
	 * @jsp.attribute required="false"
	 * rtexprvalue="true"
	 * type="java.lang.Boolean"
	 * description="Gets whether or not to generate the dynamic JavaScript"
	 */
	public Boolean getDynamicJavascript() {
		return dynamicJavascript;
	}

	/**
	 * Sets whether or not to generate the dynamic
	 * JavaScript.  If this is set to 'true', which
	 * is the default, the dynamic JavaScript will be generated.
     *
     * @param dynamicJavascript whether to generate the dynamic JavaScript
	 */
	public void setDynamicJavascript(Boolean dynamicJavascript) {
		this.dynamicJavascript = dynamicJavascript;
	}

	/**
	 * Gets whether or not to delimit the
	 * JavaScript with html comments.  If this is set to 'true', which
	 * is the default, the htmlComment will be surround the JavaScript.
	 *
     * @return whether to delimit the JavaScript with html comments
	 * @jsp.attribute required="false"
	 * rtexprvalue="true"
	 * type="java.lang.Boolean"
	 * description=" Gets whether or not to delimit the JavaScript with html comments"
	 */
	public Boolean getHtmlComment() {
		return htmlComment;
	}

	/**
	 * Sets whether or not to delimit the
	 * JavaScript with html comments.  If this is set to 'true', which
	 * is the default, the htmlComment will be surround the JavaScript.
     *
     * @param htmlComment whether to delimit the JavaScript with html comments
	 */
	public void setHtmlComment(Boolean htmlComment) {
		this.htmlComment = htmlComment;
	}

	/**
	 * Gets the src attribute's value when defining
	 * the html script element.
	 *
     * @return src value
	 * @jsp.attribute required="false"
	 * rtexprvalue="true"
	 * type="java.lang.String"
	 * description="Gets the src attribute's value when defining the html script element"
	 */
	public String getSrc() {
		return src;
	}

	/**
	 * Sets the src attribute's value when defining
	 * the html script element. The src attribute is only recognized
	 * when the formName attribute is specified.
     *
     * @param src src value to set
	 */
	public void setSrc(String src) {
		this.src = src;
	}

	/**
	 * Returns the cdata setting true or false
	 *
	 * @return String - <code>Boolean.TRUE</code> if JavaScript will be hidden in a CDATA section
	 * @jsp.attribute required="false"
	 * rtexprvalue="true"
	 * type="java.lang.Boolean"
	 * description="Returns the cdata setting 'true' or 'false'"
	 */
	public Boolean getCdata() {
		return cdata;
	}

	/**
	 * Sets the cdata status.
	 *
	 * @param cdata The cdata to set
     * @see #getCdata()
	 */
	public void setCdata(Boolean cdata) {
		this.cdata = cdata;
	}

	/**
	 * Gets whether or not the &lt;script&gt; element will include the
	 * language attribute.
	 *
	 * @return <code>Boolean.TRUE</code> if language attribute will be included.
	 * @jsp.attribute required="false"
	 * rtexprvalue="true"
	 * type="java.lang.Boolean"
	 * description="Gets whether or not the 'script' element will include the language attribute"
	 */
	public Boolean getScriptLanguage() {
		return this.scriptLanguage;
	}

	/**
	 * Sets whether or not the &lt;script&gt; element will include the
	 * language attribute.
     *
     * @param scriptLanguage <code>Boolean.TRUE</code> if language attribute will be included.
	 */
	public void setScriptLanguage(Boolean scriptLanguage) {
		this.scriptLanguage = scriptLanguage;
	}

	/**
	 * Renders the JavaScript for to perform validations based on the form name.
	 *
	 * @throws JspException if a JSP exception has occurred
	 */
	public void doTag() throws JspException, IOException {
		PageContext pageContext = (PageContext) getJspContext();

		if ( dynamicJavascript == null ) {
			dynamicJavascript = Boolean.TRUE;
		}

		if ( staticJavascript == null ) {
			staticJavascript = Boolean.TRUE;
		}

		if ( cdata == null ) {
			cdata = Boolean.TRUE;
		}

		if ( htmlComment == null ) {
			htmlComment = Boolean.TRUE;
		}

		if ( scriptLanguage == null ) {
			scriptLanguage = Boolean.TRUE;
		}

		TagUtils.getInstance().write(pageContext, renderJavascript());
	}

	/**
	 * Returns fully rendered JavaScript.
	 */
	protected String renderJavascript() throws JspException {
		PageContext pageContext = (PageContext) getJspContext();
		StringBuffer results = new StringBuffer();

		TagUtils tagUtils = TagUtils.getInstance();
		ModuleConfig config = tagUtils.getModuleConfig(pageContext);
		ValidatorResources resources =
		        (ValidatorResources) pageContext.getAttribute(ValidatorPlugIn.VALIDATOR_KEY + config.getPrefix(), PageContext.APPLICATION_SCOPE);

		Locale locale = tagUtils.getUserLocale(pageContext, null);

		Form form = resources.getForm(locale, formName);

		if ( dynamicJavascript.booleanValue() && form == null ) {
			throw new JspException("No form found under '" + formName + "' in locale '" + locale + "'");
		}

		if ( form != null ) {
			if ( dynamicJavascript.booleanValue() ) {
				results.append(createDynamicJavascript(config, resources, locale, form));
			} else if ( staticJavascript.booleanValue() ) {
				results.append(renderStartElement());
				if ( htmlComment.booleanValue() ) {
					results.append(HTML_BEGIN_COMMENT);
				}
			}
		}

		if ( staticJavascript.booleanValue() ) {
			results.append(getJavascriptStaticMethods(resources));
		}

		if ( form != null && (dynamicJavascript.booleanValue() || staticJavascript.booleanValue()) ) {
			results.append(getJavascriptEnd());
		}

		return results.toString();
	}

	/**
	 * Generates the dynamic JavaScript for the form.
	 *
	 * @param config    module config
	 * @param resources validator resources
	 * @param locale    locale
	 * @param form      form for which to generate JavaScript
     * @return generated JavaScript code
	 */
	private String createDynamicJavascript(ModuleConfig config, ValidatorResources resources, Locale locale, Form form) throws JspException {

		PageContext pageContext = (PageContext) getJspContext();

		StringBuffer results = new StringBuffer();

		ApplicationResources messages = ApplicationResources.getInstance(pageContext.getServletContext());

		List actions = createActionList(resources, form);

		final String methods = createMethods(actions, stopOnError(config));

		String formName = form.getName();
		jsFormName = formName;
		if ( jsFormName.charAt(0) == '/' ) {
			String mappingName = TagUtils.getInstance().getActionMappingName(jsFormName);
			ActionMapping mapping = (ActionMapping) config.findActionConfig(mappingName);
			if ( mapping == null ) {
				JspException e = new JspException("No action mapping found with name '" + mappingName + "'");
				throw e;
			}
			jsFormName = mapping.getAttribute();
		}

		results.append(getJavascriptBegin(methods));

		for ( Iterator i = actions.iterator(); i.hasNext(); ) {
			ValidatorAction va = (ValidatorAction) i.next();
			int jscriptVar = 0;
			String functionName = null;

			if ( va.getJsFunctionName() != null
			        && va.getJsFunctionName().length() > 0 ) {
				functionName = va.getJsFunctionName();
			} else {
				functionName = va.getName();
			}

			results.append("    function " + jsFormName + "_" + functionName + " () { \n");
			for ( Iterator j = form.getFields().iterator(); j.hasNext(); ) {
				Field field = (Field) j.next();

				// Skip indexed fields for now until there is a good way to handle
				// error messages (and the length of the list (could retrieve from scope?))
				if ( field.isIndexed()
				        || field.getPage() != page.intValue()
				        || !field.isDependency(va.getName()) ) {

					continue;
				}

				String message = getMessage(messages, locale, va, field);

				message = (message != null) ? message : "";

				// prefix variable with 'a' to make it a legal identifier
				results.append("     this.a"
				        + jscriptVar++
				        + " = new Array(\""
				        + field.getKey()
				        + "\", \""
				        + escapeQuotes(message)
				        + "\", ");

				results.append("new Function (\"varName\", \"");

				Map vars = field.getVars();
				// Loop through the field's variables.
				Iterator varsIterator = vars.keySet().iterator();
				while ( varsIterator.hasNext() ) {
					String varName = (String) varsIterator.next();
					Var var = (Var) vars.get(varName);
					String varValue = var.getValue();
					String jsType = var.getJsType();

					// skip requiredif variables field, fieldIndexed, fieldTest, fieldValue
					if ( varName.startsWith("field") ) {
						continue;
					}

					if ( Var.JSTYPE_INT.equalsIgnoreCase(jsType) ) {
						results.append("this."
						        + varName
						        + "="
						        + ValidatorUtils.replace(varValue, "\\", "\\\\")
						        + "; ");
					} else if ( Var.JSTYPE_REGEXP.equalsIgnoreCase(jsType) ) {
						results.append("this."
						        + varName
						        + "=/"
						        + ValidatorUtils.replace(varValue, "\\", "\\\\")
						        + "/; ");
					} else if ( Var.JSTYPE_STRING.equalsIgnoreCase(jsType) ) {
						results.append("this."
						        + varName
						        + "='"
						        + ValidatorUtils.replace(varValue, "\\", "\\\\")
						        + "'; ");
						// So everyone using the latest format doesn't need to change their xml files immediately.
					} else if ( "mask".equalsIgnoreCase(varName) ) {
						results.append("this."
						        + varName
						        + "=/"
						        + ValidatorUtils.replace(varValue, "\\", "\\\\")
						        + "/; ");
					} else {
						results.append("this."
						        + varName
						        + "='"
						        + ValidatorUtils.replace(varValue, "\\", "\\\\")
						        + "'; ");
					}
				}

				results.append(" return this[varName];\"));\n");
			}
			results.append("    } \n\n");
		}

		return results.toString();
	}

	private String escapeQuotes(String in) {
		if ( in == null || in.indexOf("\"") == -1 ) {
			return in;
		}
		StringBuffer buffer = new StringBuffer();
		StringTokenizer tokenizer = new StringTokenizer(in, "\"", true);

		while ( tokenizer.hasMoreTokens() ) {
			String token = tokenizer.nextToken();
			if ( token.equals("\"") ) {
				buffer.append("\\");
			}
			buffer.append(token);
		}

		return buffer.toString();
	}

	/**
	 * Determines if validations should stop on an error.
	 *
	 * @param config The <code>ModuleConfig</code> used to lookup the
	 *               stopOnError setting.
	 * @return <code>true</code> if validations should stop on errors.
	 */
	private boolean stopOnError(ModuleConfig config) {
		PageContext pageContext = (PageContext) getJspContext();
		Object stopOnErrorObj = pageContext.getAttribute(ValidatorPlugIn.STOP_ON_ERROR_KEY + '.' + config.getPrefix(), PageContext.APPLICATION_SCOPE);

		boolean stopOnError = true;

		if ( stopOnErrorObj instanceof Boolean ) {
			stopOnError = ((Boolean) stopOnErrorObj).booleanValue();
		}

		return stopOnError;
	}

	/**
	 * Creates the JavaScript methods list from the given actions.
	 *
	 * @param actions     A List of ValidatorAction objects.
	 * @param stopOnError If true, behaves like released version of struts 1.1
	 *                    and stops after first error. If false, evaluates all validations.
	 * @return JavaScript methods concatanated with AND operator
	 */
	private String createMethods(List actions, boolean stopOnError) {
		StringBuffer methods = new StringBuffer();
		final String methodOperator = stopOnError ? " && " : " & ";

		Iterator i = actions.iterator();
		while ( i.hasNext() ) {
			ValidatorAction va = (ValidatorAction) i.next();

			if ( methods.length() > 0 ) {
				methods.append(methodOperator);
			}
			methods.append(va.getMethod()).append("(form)");
		}

		return methods.toString();
	}

	/**
	 * Gets list of actions for the given Form.
	 *
	 * @param resources validator resources
	 * @param form      form
	 * @return A sorted List of ValidatorAction objects.
	 */
	private List createActionList(ValidatorResources resources, Form form) {

		List actionMethods = new ArrayList();

		Iterator i = form.getFields().iterator();
		while ( i.hasNext() ) {
			Field field = (Field) i.next();

			for ( Iterator j = field.getDependencyList().iterator(); j.hasNext(); ) {
				Object o = j.next();

				if ( o != null && !actionMethods.contains(o) ) {
					actionMethods.add(o);
				}
			}
		}

		List actions = new ArrayList();

		// Create list of ValidatorActions based on actionMethods
		i = actionMethods.iterator();
		while ( i.hasNext() ) {
			String depends = (String) i.next();
			ValidatorAction va = resources.getValidatorAction(depends);

			// throw nicer NPE for easier debugging
			if ( va == null ) {
				throw new NullPointerException("Depends string \""
				        + depends
				        + "\" was not found in validator-rules.xml.");
			}

			if ( va.getJavascript() != null && va.getJavascript().length() > 0 ) {
				actions.add(va);
			} else {
				i.remove();
			}
		}

		Collections.sort(actions, actionComparator);

		return actions;
	}


	/**
	 * Returns the opening script element and some initial javascript.
     *
     * @param methods contains methods concatenated with AND operator
     * @return beginning javascript
	 */
	protected String getJavascriptBegin(String methods) {
		StringBuffer sb = new StringBuffer();
		String name = jsFormName.replace('/', '_'); // remove any '/' characters
		name =
		        jsFormName.substring(0, 1).toUpperCase()
		        + jsFormName.substring(1, jsFormName.length());

		sb.append(this.renderStartElement());

		if ( this.isXhtml() && cdata.booleanValue() ) {
			sb.append("<![CDATA[\r\n");
		}

		if ( !this.isXhtml() && "true".equals(htmlComment) ) {
			sb.append(HTML_BEGIN_COMMENT);
		}
		sb.append("\n     var bCancel = false; \n\n");

		if ( methodName == null || methodName.length() == 0 ) {
			sb.append("    function validate"
			        + name
			        + "(form) {                                                                   \n");
		} else {
			sb.append("    function "
			        + methodName
			        + "(form) {                                                                   \n");
		}
		sb.append("        if (bCancel) \n");
		sb.append("      return true; \n");
		sb.append("        else \n");

		// Always return true if there aren't any Javascript validation methods
		if ( methods == null || methods.length() == 0 ) {
			sb.append("       return true; \n");
		} else {
			//Making Sure that Bitwise operator works:
			sb.append(" var formValidationResult;\n");
			sb.append("       formValidationResult = " + methods + "; \n");
			sb.append("     return (formValidationResult == 1);\n");
		}

		sb.append("   } \n\n");

		return sb.toString();
	}

    /**
     * Returns static JS methods
     *
     * @param resources validator resources
     * @return static methods
     */
	protected String getJavascriptStaticMethods(ValidatorResources resources) {
		StringBuffer sb = new StringBuffer();

		sb.append("\n\n");

		Iterator actions = resources.getValidatorActions().values().iterator();
		while ( actions.hasNext() ) {
			ValidatorAction va = (ValidatorAction) actions.next();
			if ( va != null ) {
				String javascript = va.getJavascript();
				if ( javascript != null && javascript.length() > 0 ) {
					sb.append(javascript + "\n");
				}
			}
		}

		return sb.toString();
	}

	/**
	 * Returns the closing script element.
     *
     * @return closing element
	 */
	protected String getJavascriptEnd() {
		StringBuffer sb = new StringBuffer();

		sb.append("\n");
		if ( !this.isXhtml() && "true".equals(htmlComment) ) {
			sb.append(HTML_END_COMMENT);
		}

		if ( this.isXhtml() && cdata.booleanValue() ) {
			sb.append("]]>\r\n");
		}

		sb.append("</script>\n\n");

		return sb.toString();
	}

	/**
	 * Constructs the beginning &lt;script&gt; element depending on XHTML
	 * status.
     *
     * @return starting element
	 */
	protected String renderStartElement() {
		StringBuffer start = new StringBuffer("<script type=\"text/javascript\"");

		// there is no language attribute in XHTML
		if ( !this.isXhtml() && scriptLanguage.booleanValue() ) {
			start.append(" language=\"Javascript1.1\"");
		}

		if ( this.src != null ) {
			start.append(" src=\"" + src + "\"");
		}

		start.append("> \n");
		return start.toString();
	}

	/**
	 * Returns <code>true</code> if this is an xhtml page.
     *
     * @return whether xhtml is in use
	 */
	private boolean isXhtml() {
		return TagUtils.getInstance().isXhtml((PageContext) getJspContext());
	}

	/**
	 * Gets the locale sensitive message based on the
	 * <code>ValidatorAction</code> message and the <code>Field</code>'s
	 * arg objects.
	 *
	 * @param messages The Message resources
	 * @param locale   The locale
	 * @param va       The Validator Action
	 * @param field    The Validator Field
	 */
	public static String getMessage(ApplicationResources messages, Locale locale, ValidatorAction va, Field field) {

		String args[] = getArgs(va.getName(), messages, locale, field);

		String fieldMsg = field.getMsg(va.getName());
		String msg = fieldMsg != null ? fieldMsg : va.getMsg();

		return messages.getMessage(locale, msg, args);
	}

	/**
	 * Gets the message arguments based on the current
	 * <code>ValidatorAction</code> and <code>Field</code>.
	 *
	 * @param actionName action name
	 * @param messages   message resources
	 * @param locale     the locale
	 * @param field      the validator field
	 */
	public static String[] getArgs(String actionName, ApplicationResources messages, Locale locale, Field field) {

		String[] argMessages = new String[4];

		Arg[] args = new Arg[]{field.getArg(actionName, 0), field.getArg(actionName, 1), field.getArg(actionName, 2), field.getArg(actionName, 3)};

		for ( int i = 0; i < args.length; i++ ) {
			if ( args[i] == null ) {
				continue;
			}

			if ( args[i].isResource() ) {
				String msg = messages.getMessage(locale, args[i].getKey());
				argMessages[i] = msg != null ? msg : "";
			} else {
				argMessages[i] = args[i].getKey();
			}

		}

		return argMessages;
	}


}
